home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / python2.6 / dist-packages / apport / ui.pyc (.txt) < prev   
Encoding:
Python Compiled Bytecode  |  2009-10-12  |  51.9 KB  |  1,583 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Abstract Apport user interface.
  5.  
  6. This encapsulates the workflow and common code for any user interface
  7. implementation (like GTK, Qt, or CLI).
  8.  
  9. Copyright (C) 2007 Canonical Ltd.
  10. Author: Martin Pitt <martin.pitt@ubuntu.com>
  11.  
  12. This program is free software; you can redistribute it and/or modify it
  13. under the terms of the GNU General Public License as published by the
  14. Free Software Foundation; either version 2 of the License, or (at your
  15. option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
  16. the full text of the license.
  17. '''
  18. import glob
  19. import sys
  20. import os.path as os
  21. import optparse
  22. import time
  23. import traceback
  24. import locale
  25. import gettext
  26. import re
  27. import pwd
  28. import errno
  29. import urllib
  30. import zlib
  31. import subprocess
  32. import threading
  33. import webbrowser
  34. from gettext import gettext as _
  35. import apport
  36. import apport.fileutils as apport
  37. import REThread
  38. from apport.crashdb import get_crashdb
  39.  
  40. def thread_collect_info(report, reportfile, package):
  41.     '''Encapsulate call to add_*_info() and update given report,
  42.     so that this function is suitable for threading.
  43.  
  44.     If reportfile is not None, the file is written back with the new data.'''
  45.     report.add_gdb_info()
  46.     if not package:
  47.         if report.has_key('ExecutablePath'):
  48.             package = apport.fileutils.find_file_package(report['ExecutablePath'])
  49.         else:
  50.             raise KeyError, 'called without a package, and report does not have ExecutablePath'
  51.     report.has_key('ExecutablePath')
  52.     report.add_package_info(package)
  53.     report.add_os_info()
  54.     report.add_hooks_info()
  55.     title = report.standard_title()
  56.     if title:
  57.         report['Title'] = title
  58.     
  59.     if 'Package' not in report or not apport.packaging.is_distro_package(report['Package'].split()[0]):
  60.         if 'APPORT_REPORT_THIRDPARTY' in os.environ or apport.fileutils.get_config('main', 'thirdparty', False, bool = True):
  61.             report['ThirdParty'] = 'True'
  62.         else:
  63.             report['UnreportableReason'] = _('This is not a genuine %s package') % report['DistroRelease'].split()[0]
  64.     
  65.     if report['ProblemType'] == 'Crash' and 'APPORT_IGNORE_OBSOLETE_PACKAGES' not in os.environ:
  66.         old_pkgs = report.obsolete_packages()
  67.         if old_pkgs:
  68.             report['UnreportableReason'] = _('You have some obsolete package versions installed. Please upgrade the following packages and check if the problem still occurs:\n\n%s') % ', '.join(old_pkgs)
  69.         
  70.     
  71.     report.anonymize()
  72.     if reportfile:
  73.         f = open(reportfile, 'a')
  74.         os.chmod(reportfile, 0)
  75.         report.write(f, only_new = True)
  76.         f.close()
  77.         apport.fileutils.mark_report_seen(reportfile)
  78.         os.chmod(reportfile, 384)
  79.     
  80.  
  81.  
  82. class UserInterface:
  83.     '''Abstract base class for encapsulating the workflow and common code for
  84.        any user interface implementation (like GTK, Qt, or CLI).
  85.  
  86.        A concrete subclass must implement all the abstract ui_* methods.'''
  87.     
  88.     def __init__(self):
  89.         '''Initialize program state and parse command line options.'''
  90.         self.gettext_domain = 'apport'
  91.         self.report = None
  92.         self.report_file = None
  93.         self.cur_package = None
  94.         
  95.         try:
  96.             self.crashdb = get_crashdb(None)
  97.         except ImportError:
  98.             e = None
  99.             print >>sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e
  100.             sys.exit(1)
  101.  
  102.         gettext.textdomain(self.gettext_domain)
  103.         self.parse_argv()
  104.  
  105.     
  106.     def run_crashes(self):
  107.         '''Present all currently pending crash reports to the user, ask him
  108.         what to do about them, and offer to file bugs for them.
  109.         
  110.         Return True if at least one crash report was processed, False
  111.         otherwise.'''
  112.         result = False
  113.         for f in apport.fileutils.get_new_reports():
  114.             self.run_crash(f)
  115.             result = True
  116.         
  117.         return result
  118.  
  119.     
  120.     def run_crash(self, report_file, confirm = True):
  121.         '''Present given crash report to the user, ask him what to do about it,
  122.         and offer to file a bug for it.
  123.         
  124.         If confirm is False, the user will not be asked whether to report the
  125.         problem.'''
  126.         self.report_file = report_file
  127.         
  128.         try:
  129.             
  130.             try:
  131.                 apport.fileutils.mark_report_seen(report_file)
  132.             except OSError:
  133.                 pass
  134.  
  135.             if not self.load_report(report_file):
  136.                 return None
  137.             if 'Ignore' in self.report:
  138.                 return None
  139.             if self.report.get('ProblemType') == 'Crash' and 'Signal' in self.report and 'CoreDump' not in self.report and 'Stacktrace' not in self.report:
  140.                 subject = os.path.basename(self.report.get('ExecutablePath', _('unknown program')))
  141.                 heading = _('Sorry, the program "%s" closed unexpectedly') % subject
  142.                 self.ui_error_message(_('Problem in %s') % subject, '%s\n\n%s' % (heading, _('Your computer does not have enough free memory to automatically analyze the problem and send a report to the developers.')))
  143.                 return None
  144.             if self.report.has_key('UnsupportableReason'):
  145.                 self.ui_info_message(_('Unreportable problem'), _('The current configuration cannot be supported:\n\n%s') % self.report['UnsupportableReason'])
  146.                 return None
  147.             if not confirm:
  148.                 pass
  149.             elif self.report.get('ProblemType') == 'Package':
  150.                 response = self.ui_present_package_error()
  151.                 if response == 'cancel':
  152.                     return None
  153.                 if not response == 'report':
  154.                     raise AssertionError
  155.             elif self.report.get('ProblemType') == 'KernelCrash':
  156.                 response = self.ui_present_kernel_error()
  157.                 if response == 'cancel':
  158.                     return None
  159.                 if not response == 'report':
  160.                     raise AssertionError
  161.             elif self.report.get('ProblemType') == 'KernelOops':
  162.                 response = self.ui_present_kernel_error()
  163.                 if response == 'cancel':
  164.                     return None
  165.                 if not response == 'report':
  166.                     raise AssertionError
  167.             else:
  168.                 
  169.                 try:
  170.                     desktop_entry = self.get_desktop_entry()
  171.                 except ValueError:
  172.                     response == 'cancel'
  173.                     response == 'cancel'
  174.                     response == 'cancel'
  175.                     self.ui_error_message(_('Invalid problem report'), _('The report belongs to a package that is not installed.'))
  176.                     self.ui_shutdown()
  177.                     return None
  178.                     response == 'cancel'
  179.  
  180.                 response = self.ui_present_crash(desktop_entry)
  181.                 if not response.has_key('action'):
  182.                     raise AssertionError
  183.                 if not response.has_key('blacklist'):
  184.                     raise AssertionError
  185.                 if response['action'] == 'cancel':
  186.                     return None
  187.                 if response['action'] == 'restart':
  188.                     self.restart()
  189.                     return None
  190.                 if not response['action'] == 'report':
  191.                     raise AssertionError
  192.             
  193.             try:
  194.                 self.collect_info()
  195.             except (IOError, zlib.error):
  196.                 response['action'] == 'report'
  197.                 response['action'] == 'report'
  198.                 response['action'] == 'restart'
  199.                 self.report = None
  200.                 self.ui_error_message(_('Invalid problem report'), _('This problem report is damaged and cannot be processed.'))
  201.                 return False
  202.                 response['action'] == 'cancel'
  203.                 except ValueError:
  204.                     response == 'cancel' if response['blacklist'] else response.has_key('action')
  205.                     response == 'cancel' if response['blacklist'] else response.has_key('action')
  206.                     response == 'cancel'
  207.                     self.ui_error_message(_('Invalid problem report'), _('The report belongs to a package that is not installed.'))
  208.                     self.ui_shutdown()
  209.                     return None
  210.                     response == 'cancel'
  211.                 elif self.report.has_key('UnreportableReason'):
  212.                     self.ui_info_message(_('Problem in %s') % self.report['Package'].split()[0], _('The problem cannot be reported:\n\n%s') % self.report['UnreportableReason'])
  213.                     return None
  214.  
  215.             'Stacktrace' not in self.report
  216.             if self.handle_duplicate():
  217.                 return None
  218.             self.file_report()
  219.         except IOError:
  220.             e = None
  221.             if e.errno in (errno.EPERM, errno.EACCES):
  222.                 self.ui_error_message(_('Invalid problem report'), _('You are not allowed to access this problem report.'))
  223.                 sys.exit(1)
  224.             elif e.errno == errno.ENOSPC:
  225.                 self.ui_error_message(_('Error'), _('There is not enough disk space available to process this report.'))
  226.                 sys.exit(1)
  227.             else:
  228.                 self.ui_error_message(_('Invalid problem report'), e.strerror)
  229.                 sys.exit(1)
  230.         except OSError:
  231.             e = None
  232.             if e.errno == errno.ENOMEM:
  233.                 print >>sys.stderr, 'Out of memory, aborting'
  234.                 sys.exit(1)
  235.             else:
  236.                 raise 
  237.             e.errno == errno.ENOMEM
  238.  
  239.  
  240.     
  241.     def run_report_bug(self):
  242.         '''Report a bug.
  243.  
  244.         If a pid is given on the command line, the report will contain runtime
  245.         debug information. Either a package or a pid must be specified.'''
  246.         if not (self.options.package) and not (self.options.pid):
  247.             self.ui_error_message(_('No package specified'), _('You need to specify a package or a PID. See --help for more information.'))
  248.             return False
  249.         self.report = apport.Report('Bug')
  250.         
  251.         try:
  252.             if self.options.pid:
  253.                 self.report.add_proc_info(self.options.pid)
  254.             else:
  255.                 self.report.add_proc_environ()
  256.         except OSError:
  257.             not (self.options.pid)
  258.             e = not (self.options.pid)
  259.             if e.errno == errno.ENOENT:
  260.                 return False
  261.             if e.errno == errno.EACCES:
  262.                 self.ui_error_message(_('Permission denied'), _('The specified process does not belong to you. Please run this program as the process owner or as root.'))
  263.                 return False
  264.             raise 
  265.         except:
  266.             e.errno == errno.EACCES
  267.  
  268.         if self.options.package == 'linux':
  269.             self.cur_package = apport.packaging.get_kernel_package()
  270.         else:
  271.             self.cur_package = self.options.package
  272.         
  273.         try:
  274.             self.collect_info()
  275.         except ValueError:
  276.             e = None
  277.             if str(e) == 'package does not exist':
  278.                 self.ui_error_message(_('Invalid problem report'), _('Package %s does not exist') % self.cur_package)
  279.                 return False
  280.             raise 
  281.         except:
  282.             str(e) == 'package does not exist'
  283.  
  284.         if not self.handle_duplicate():
  285.             
  286.             try:
  287.                 del self.report['ProcCmdline']
  288.             except KeyError:
  289.                 str(e) == 'package does not exist'
  290.                 str(e) == 'package does not exist'
  291.             except:
  292.                 str(e) == 'package does not exist'
  293.  
  294.             response = self.ui_present_report_details()
  295.             if response != 'cancel':
  296.                 self.file_report()
  297.             
  298.         
  299.         return True
  300.  
  301.     
  302.     def run_argv(self):
  303.         '''Call appopriate run_* method according to command line arguments.
  304.         
  305.         Return True if at least one report has been processed, and False
  306.         otherwise.'''
  307.         if self.options.filebug:
  308.             return self.run_report_bug()
  309.         if self.options.crash_file:
  310.             
  311.             try:
  312.                 self.run_crash(self.options.crash_file, False)
  313.             except OSError:
  314.                 self.options.filebug
  315.                 e = self.options.filebug
  316.                 self.ui_error_message(_('Invalid problem report'), str(e))
  317.             except:
  318.                 self.options.filebug
  319.  
  320.             return True
  321.         return self.run_crashes()
  322.  
  323.     
  324.     def parse_argv(self):
  325.         '''Parse command line options and return (options,
  326.         args) tuple.'''
  327.         optparser = optparse.OptionParser('%prog [options]')
  328.         optparser.add_option('-f', '--file-bug', help = 'Start in bug filing mode. Requires --package and an optional --pid, or just a --pid', action = 'store_true', dest = 'filebug', default = False)
  329.         optparser.add_option('-p', '--package', help = 'Specify package name in --file-bug mode. This is optional if a --pid is specified.', action = 'store', type = 'string', dest = 'package', default = None)
  330.         optparser.add_option('-P', '--pid', help = 'Specify a running program in --file-bug mode. If this is specified, the bug report will contain more information.', action = 'store', type = 'int', dest = 'pid', default = None)
  331.         optparser.add_option('-c', '--crash-file', help = 'Report the crash from given .crash file instead of the pending ones in ' + apport.fileutils.report_dir, action = 'store', type = 'string', dest = 'crash_file', default = None, metavar = 'PATH')
  332.         (self.options, self.args) = optparser.parse_args()
  333.  
  334.     
  335.     def format_filesize(self, size):
  336.         """Format the given integer as humanly readable and i18n'ed file size."""
  337.         if size < 1048576:
  338.             return locale.format('%.1f KiB', size / 1024)
  339.         if size < 1073741824:
  340.             return locale.format('%.1f MiB', size / 1.04858e+06)
  341.         return locale.format('%.1f GiB', size / float(1073741824))
  342.  
  343.     
  344.     def get_complete_size(self):
  345.         '''Return the size of the complete report.'''
  346.         
  347.         try:
  348.             return self.complete_size
  349.         except AttributeError:
  350.             size = 0
  351.             for k in self.report:
  352.                 if self.report[k]:
  353.                     size += len(self.report[k])
  354.                     continue
  355.             
  356.             return size
  357.  
  358.  
  359.     
  360.     def get_reduced_size(self):
  361.         '''Return the size of the reduced report.'''
  362.         size = 0
  363.         for k in self.report:
  364.             if k != 'CoreDump':
  365.                 if self.report[k]:
  366.                     size += len(self.report[k])
  367.                 
  368.             self.report[k]
  369.         
  370.         return size
  371.  
  372.     
  373.     def restart(self):
  374.         '''Reopen the crashed application.'''
  375.         if not self.report.has_key('ProcCmdline'):
  376.             raise AssertionError
  377.         if os.fork() == 0:
  378.             os.setsid()
  379.             os.execlp('sh', 'sh', '-c', self.report.get('RespawnCommand', self.report['ProcCmdline']))
  380.             sys.exit(1)
  381.         
  382.  
  383.     
  384.     def collect_info(self):
  385.         '''Collect missing information about the report from the system and
  386.         display a progress dialog in the meantime.
  387.  
  388.         In particular, this adds OS, package and gdb information and checks bug
  389.         patterns.'''
  390.         if not (self.cur_package) and not self.report.has_key('ExecutablePath'):
  391.             self.report.add_os_info()
  392.         elif self.report['ProblemType'] == 'Crash' and 'Stacktrace' in self.report:
  393.             return None
  394.         self.ui_start_info_collection_progress()
  395.         if not self.report.has_key('Stacktrace'):
  396.             icthread = REThread.REThread(target = thread_collect_info, name = 'thread_collect_info', args = (self.report, self.report_file, self.cur_package))
  397.             icthread.start()
  398.             while icthread.isAlive():
  399.                 self.ui_pulse_info_collection_progress()
  400.                 
  401.                 try:
  402.                     icthread.join(0.1)
  403.                 continue
  404.                 except KeyboardInterrupt:
  405.                     sys.exit(1)
  406.                     continue
  407.                 
  408.  
  409.                 None<EXCEPTION MATCH>KeyboardInterrupt
  410.             icthread.exc_raise()
  411.         
  412.         if self.report.has_key('CrashDB'):
  413.             self.crashdb = get_crashdb(None, self.report['CrashDB'])
  414.         
  415.         if self.report['ProblemType'] == 'KernelCrash' and self.report['ProblemType'] == 'KernelOops' or self.report.has_key('Package'):
  416.             bpthread = REThread.REThread(target = self.report.search_bug_patterns, args = (self.crashdb.get_bugpattern_baseurl(),))
  417.             bpthread.start()
  418.             while bpthread.isAlive():
  419.                 self.ui_pulse_info_collection_progress()
  420.                 
  421.                 try:
  422.                     bpthread.join(0.1)
  423.                 continue
  424.                 except KeyboardInterrupt:
  425.                     sys.exit(1)
  426.                     continue
  427.                 
  428.  
  429.                 None<EXCEPTION MATCH>KeyboardInterrupt
  430.             bpthread.exc_raise()
  431.             if bpthread.return_value():
  432.                 self.report['BugPatternURL'] = bpthread.return_value()
  433.             
  434.         
  435.         self.ui_stop_info_collection_progress()
  436.         if ('SourcePackage' not in self.report or not self.report['ProblemType'].startswith('Kernel')) and 'Package' not in self.report:
  437.             self.ui_error_message(_('Invalid problem report'), _('Could not determine the package or source package name.'))
  438.             self.ui_shutdown()
  439.             sys.exit(1)
  440.         
  441.  
  442.     
  443.     def open_url(self, url):
  444.         '''Open the given URL in a new browser window.
  445.  
  446.         Display an error dialog if everything fails.'''
  447.         (r, w) = os.pipe()
  448.         if os.fork() > 0:
  449.             os.close(w)
  450.             (pid, status) = os.wait()
  451.             if status:
  452.                 title = _('Unable to start web browser')
  453.                 error = _('Unable to start web browser to open %s.' % url)
  454.                 message = os.fdopen(r).readline()
  455.                 if message:
  456.                     error += '\n' + message
  457.                 
  458.                 self.ui_error_message(title, error)
  459.             
  460.             
  461.             try:
  462.                 os.close(r)
  463.             except OSError:
  464.                 pass
  465.  
  466.             return None
  467.         os.setsid()
  468.         os.close(r)
  469.         
  470.         try:
  471.             uid = int(os.getenv('SUDO_UID'))
  472.             gid = int(os.getenv('SUDO_GID'))
  473.             sudo_prefix = [
  474.                 'sudo',
  475.                 '-H',
  476.                 '-u',
  477.                 '#' + str(uid)]
  478.         except TypeError:
  479.             os.fork() > 0
  480.             os.fork() > 0
  481.             uid = os.getuid()
  482.             gid = None
  483.             sudo_prefix = []
  484.         except:
  485.             os.fork() > 0
  486.  
  487.         
  488.         try:
  489.             
  490.             try:
  491.                 if os.getenv('DISPLAY') and subprocess.call([
  492.                     'pgrep',
  493.                     '-x',
  494.                     '-u',
  495.                     str(uid),
  496.                     'ksmserver'], stdout = subprocess.PIPE, stderr = subprocess.PIPE) == 0:
  497.                     subprocess.call(sudo_prefix + [
  498.                         'kfmclient',
  499.                         'openURL',
  500.                         url])
  501.                     sys.exit(0)
  502.             except OSError:
  503.                 os.fork() > 0
  504.                 os.fork() > 0
  505.             except:
  506.                 os.fork() > 0
  507.  
  508.             
  509.             try:
  510.                 if os.getenv('DISPLAY') and subprocess.call([
  511.                     'pgrep',
  512.                     '-x',
  513.                     '-u',
  514.                     str(uid),
  515.                     'gnome-panel|gconfd-2'], stdout = subprocess.PIPE, stderr = subprocess.PIPE) == 0:
  516.                     gct = subprocess.Popen(sudo_prefix + [
  517.                         'gconftool',
  518.                         '--get',
  519.                         '/desktop/gnome/url-handlers/http/command'], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
  520.                     if gct.returncode == 0:
  521.                         preferred_browser = gct.communicate()[0]
  522.                         browser = re.match('((firefox|seamonkey|flock)[^\\s]*)', preferred_browser)
  523.                         if browser:
  524.                             subprocess.call(sudo_prefix + [
  525.                                 browser.group(0),
  526.                                 '-new-window',
  527.                                 url])
  528.                             sys.exit(0)
  529.                         
  530.                         browser = re.match('(epiphany[^\\s]*)', preferred_browser)
  531.                         if browser:
  532.                             subprocess.call(sudo_prefix + [
  533.                                 browser.group(0),
  534.                                 '--new-window',
  535.                                 url])
  536.                             sys.exit(0)
  537.                         
  538.                     
  539.                     if subprocess.call(sudo_prefix + [
  540.                         'gnome-open',
  541.                         url]) == 0:
  542.                         sys.exit(0)
  543.                     
  544.             except OSError:
  545.                 os.fork() > 0
  546.                 os.fork() > 0
  547.             except:
  548.                 os.fork() > 0
  549.  
  550.             if uid and gid:
  551.                 os.setgroups([
  552.                     gid])
  553.                 os.setgid(gid)
  554.                 os.setuid(uid)
  555.                 os.unsetenv('SUDO_USER')
  556.                 os.environ['HOME'] = pwd.getpwuid(uid).pw_dir
  557.             
  558.             webbrowser.open(url, new = True, autoraise = True)
  559.             sys.exit(0)
  560.         except Exception:
  561.             os.fork() > 0
  562.             e = os.fork() > 0
  563.             os.write(w, str(e))
  564.             sys.exit(1)
  565.         except:
  566.             os.fork() > 0
  567.  
  568.  
  569.     
  570.     def file_report(self):
  571.         '''Upload the current report to the tracking system and guide the user
  572.         to its web page.'''
  573.         global _UserInterface__upload_progress
  574.         if self.report.get('PackageArchitecture') == self.report.get('Architecture'):
  575.             
  576.             try:
  577.                 del self.report['PackageArchitecture']
  578.             except KeyError:
  579.                 pass
  580.             except:
  581.                 None<EXCEPTION MATCH>KeyError
  582.             
  583.  
  584.         None<EXCEPTION MATCH>KeyError
  585.         _UserInterface__upload_progress = None
  586.         
  587.         def progress_callback(sent, total):
  588.             global _UserInterface__upload_progress
  589.             _UserInterface__upload_progress = float(sent) / total
  590.  
  591.         self.ui_start_upload_progress()
  592.         upthread = REThread.REThread(target = self.crashdb.upload, args = (self.report, progress_callback))
  593.         upthread.start()
  594.         while upthread.isAlive():
  595.             self.ui_set_upload_progress(_UserInterface__upload_progress)
  596.             
  597.             try:
  598.                 upthread.join(0.1)
  599.             continue
  600.             except KeyboardInterrupt:
  601.                 sys.exit(1)
  602.                 continue
  603.             
  604.  
  605.             None<EXCEPTION MATCH>KeyboardInterrupt
  606.         if upthread.exc_info():
  607.             self.ui_error_message(_('Network problem'), '%s:\n\n%s' % (_('Could not upload report data to crash database'), str(upthread.exc_info()[1])))
  608.             return None
  609.         ticket = upthread.return_value()
  610.         self.ui_stop_upload_progress()
  611.         url = self.crashdb.get_comment_url(self.report, ticket)
  612.         if url:
  613.             self.open_url(url)
  614.         
  615.  
  616.     
  617.     def load_report(self, path):
  618.         '''Load report from given path and do some consistency checks.
  619.  
  620.         This might issue an error message and return False if the report cannot
  621.         be processed, otherwise self.report is initialized and True is
  622.         returned.'''
  623.         
  624.         try:
  625.             self.report = apport.Report()
  626.             self.report.load(open(path), binary = 'compressed')
  627.         except MemoryError:
  628.             self.report = None
  629.             self.ui_error_message(_('Memory exhaustion'), _('Your system does not have enough memory to process this crash report.'))
  630.             return False
  631.             except IOError:
  632.                 e = None
  633.                 self.report = None
  634.                 self.ui_error_message(_('Invalid problem report'), e.strerror)
  635.                 return False
  636.                 except (TypeError, ValueError, zlib.error):
  637.                     self.report = None
  638.                     self.ui_error_message(_('Invalid problem report'), _('This problem report is damaged and cannot be processed.'))
  639.                     return False
  640.                 elif self.report.has_key('Package'):
  641.                     self.cur_package = self.report['Package'].split()[0]
  642.                 else:
  643.                     self.cur_package = apport.fileutils.find_file_package(self.report.get('ExecutablePath', ''))
  644.  
  645.         exe_path = self.report.get('InterpreterPath', self.report.get('ExecutablePath'))
  646.         if (not (self.cur_package) and self.report['ProblemType'] != 'KernelCrash' or self.report['ProblemType'] != 'KernelOops' or exe_path) and not os.path.exists(exe_path):
  647.             msg = _('This problem report does not apply to a packaged program.')
  648.             if self.report.has_key('ExecutablePath'):
  649.                 msg = '%s (%s)' % (msg, self.report['ExecutablePath'])
  650.             
  651.             self.report = None
  652.             self.ui_info_message(_('Invalid problem report'), msg)
  653.             return False
  654.         self.complete_size = os.path.getsize(path)
  655.         return True
  656.  
  657.     
  658.     def get_desktop_entry(self):
  659.         '''Try to get a matching .desktop file entry (xdg.DesktopEntry) for the
  660.         current self.report and return it.'''
  661.         if self.report.has_key('DesktopFile') and os.path.exists(self.report['DesktopFile']):
  662.             desktop_file = self.report['DesktopFile']
  663.         else:
  664.             desktop_file = apport.fileutils.find_package_desktopfile(self.cur_package)
  665.         if desktop_file:
  666.             
  667.             try:
  668.                 import xdg.DesktopEntry as xdg
  669.                 return xdg.DesktopEntry.DesktopEntry(desktop_file)
  670.             return None
  671.  
  672.         
  673.  
  674.     
  675.     def handle_duplicate(self):
  676.         '''Check whether the current bug report is already known as a bug
  677.         pattern, and if so, tell the user about it, open the existing bug, and
  678.         return True.'''
  679.         if not self.report.has_key('BugPatternURL'):
  680.             return False
  681.         self.ui_info_message(_('Problem already known'), _('This problem was already reported in the bug report displayed in the web browser. Please check if you can add any further information that might be helpful for the developers.'))
  682.         self.open_url(self.report['BugPatternURL'])
  683.         return True
  684.  
  685.     
  686.     def ui_present_crash(self, desktopentry):
  687.         """Inform that a crash has happened for self.report and
  688.         self.cur_package and ask about an action.
  689.  
  690.         If the package can be mapped to a desktop file, an xdg.DesktopEntry is
  691.         passed as an argument; this can be used for enhancing strings, etc.
  692.  
  693.         Return the action and options as a dictionary:
  694.  
  695.         - Valid values for the 'action' key: ignore the crash ('cancel'), restart
  696.           the crashed application ('restart'), or report a bug about the crash
  697.           ('report').
  698.         - Valid values for the 'blacklist' key: True or False (True will cause
  699.           the invocation of report.mark_ignore())."""
  700.         raise NotImplementedError, 'this function must be overridden by subclasses'
  701.  
  702.     
  703.     def ui_present_package_error(self, desktopentry):
  704.         """Inform that a package installation/upgrade failure has happened for
  705.         self.report and self.cur_package and ask about an action.
  706.  
  707.         Return the action: ignore ('cancel'), or report a bug about the problem
  708.         ('report')."""
  709.         raise NotImplementedError, 'this function must be overridden by subclasses'
  710.  
  711.     
  712.     def ui_present_kernel_error(self, desktopentry):
  713.         """Inform that a kernel crash has happened for self.report and
  714.         ask about an action.
  715.  
  716.         Return the action: ignore ('cancel'), or report a bug about the problem
  717.         ('report')."""
  718.         raise NotImplementedError, 'this function must be overridden by subclasses'
  719.  
  720.     
  721.     def ui_present_report_details(self):
  722.         """Show details of the bug report and choose between sending a complete
  723.         or reduced report.
  724.  
  725.         This function can use the get_complete_size() and get_reduced_size()
  726.         methods to determine the respective size of the data to send, and
  727.         format_filesize() to convert it to a humanly readable form.
  728.  
  729.         Return the action: send full report ('full'), send reduced report
  730.         ('reduced'), or do not send anything ('cancel')."""
  731.         raise NotImplementedError, 'this function must be overridden by subclasses'
  732.  
  733.     
  734.     def ui_info_message(self, title, text):
  735.         '''Show an information message box with given title and text.'''
  736.         raise NotImplementedError, 'this function must be overridden by subclasses'
  737.  
  738.     
  739.     def ui_error_message(self, title, text):
  740.         '''Show an error message box with given title and text.'''
  741.         raise NotImplementedError, 'this function must be overridden by subclasses'
  742.  
  743.     
  744.     def ui_start_info_collection_progress(self):
  745.         '''Open a window with an indefinite progress bar, telling the user to
  746.         wait while debug information is being collected.'''
  747.         raise NotImplementedError, 'this function must be overridden by subclasses'
  748.  
  749.     
  750.     def ui_pulse_info_collection_progress(self):
  751.         '''Advance the progress bar in the debug data collection progress
  752.         window.
  753.  
  754.         This function is called every 100 ms.'''
  755.         raise NotImplementedError, 'this function must be overridden by subclasses'
  756.  
  757.     
  758.     def ui_stop_info_collection_progress(self):
  759.         '''Close debug data collection progress window.'''
  760.         raise NotImplementedError, 'this function must be overridden by subclasses'
  761.  
  762.     
  763.     def ui_start_upload_progress(self):
  764.         '''Open a window with an definite progress bar, telling the user to
  765.         wait while debug information is being uploaded.'''
  766.         raise NotImplementedError, 'this function must be overridden by subclasses'
  767.  
  768.     
  769.     def ui_set_upload_progress(self, progress):
  770.         '''Set the progress bar in the debug data upload progress
  771.         window to the given ratio (between 0 and 1, or None for indefinite
  772.         progress).
  773.  
  774.         This function is called every 100 ms.'''
  775.         raise NotImplementedError, 'this function must be overridden by subclasses'
  776.  
  777.     
  778.     def ui_stop_upload_progress(self):
  779.         '''Close debug data upload progress window.'''
  780.         raise NotImplementedError, 'this function must be overridden by subclasses'
  781.  
  782.     
  783.     def ui_shutdown(self):
  784.         '''This is called right before terminating the program and can be used
  785.         for cleaning up.'''
  786.         pass
  787.  
  788.  
  789. if __name__ == '__main__':
  790.     import unittest
  791.     import shutil
  792.     import signal
  793.     import tempfile
  794.     from cStringIO import StringIO
  795.     import problem_report
  796.     
  797.     class _TestSuiteUserInterface(UserInterface):
  798.         '''Concrete UserInterface suitable for automatic testing.'''
  799.         
  800.         def __init__(self):
  801.             self.crashdb_conf = tempfile.NamedTemporaryFile()
  802.             print >>self.crashdb_conf, "default = 'testsuite'\ndatabases = {\n    'testsuite': { \n        'impl': 'memory',\n        'bug_pattern_base': None\n    }\n}\n"
  803.             self.crashdb_conf.flush()
  804.             os.environ['APPORT_CRASHDB_CONF'] = self.crashdb_conf.name
  805.             UserInterface.__init__(self)
  806.             self.ic_progress_active = False
  807.             self.ic_progress_pulses = 0
  808.             self.upload_progress_active = False
  809.             self.upload_progress_pulses = 0
  810.             self.present_crash_response = None
  811.             self.present_package_error_response = None
  812.             self.present_kernel_error_response = None
  813.             self.present_details_response = None
  814.             self.opened_url = None
  815.             self.clear_msg()
  816.  
  817.         
  818.         def clear_msg(self):
  819.             self.msg_title = None
  820.             self.msg_text = None
  821.             self.msg_severity = None
  822.  
  823.         
  824.         def ui_present_crash(self, desktopentry):
  825.             return self.present_crash_response
  826.  
  827.         
  828.         def ui_present_package_error(self):
  829.             return self.present_package_error_response
  830.  
  831.         
  832.         def ui_present_kernel_error(self):
  833.             return self.present_kernel_error_response
  834.  
  835.         
  836.         def ui_present_report_details(self):
  837.             return self.present_details_response
  838.  
  839.         
  840.         def ui_info_message(self, title, text):
  841.             self.msg_title = title
  842.             self.msg_text = text
  843.             self.msg_severity = 'info'
  844.  
  845.         
  846.         def ui_error_message(self, title, text):
  847.             self.msg_title = title
  848.             self.msg_text = text
  849.             self.msg_severity = 'error'
  850.  
  851.         
  852.         def ui_start_info_collection_progress(self):
  853.             self.ic_progress_pulses = 0
  854.             self.ic_progress_active = True
  855.  
  856.         
  857.         def ui_pulse_info_collection_progress(self):
  858.             if not self.ic_progress_active:
  859.                 raise AssertionError
  860.             self.ic_progress_pulses += 1
  861.  
  862.         
  863.         def ui_stop_info_collection_progress(self):
  864.             self.ic_progress_active = False
  865.  
  866.         
  867.         def ui_start_upload_progress(self):
  868.             self.upload_progress_pulses = 0
  869.             self.upload_progress_active = True
  870.  
  871.         
  872.         def ui_set_upload_progress(self, progress):
  873.             if not self.upload_progress_active:
  874.                 raise AssertionError
  875.             self.upload_progress_pulses += 1
  876.  
  877.         
  878.         def ui_stop_upload_progress(self):
  879.             self.upload_progress_active = False
  880.  
  881.         
  882.         def open_url(self, url):
  883.             self.opened_url = url
  884.  
  885.  
  886.     
  887.     class _UserInterfaceTest(unittest.TestCase):
  888.         
  889.         def setUp(self):
  890.             for v in [
  891.                 'LANG',
  892.                 'LANGUAGE',
  893.                 'LC_MESSAGES',
  894.                 'LC_ALL']:
  895.                 
  896.                 try:
  897.                     del os.environ[v]
  898.                 continue
  899.                 except KeyError:
  900.                     continue
  901.                 
  902.  
  903.             
  904.             self.orig_report_dir = apport.fileutils.report_dir
  905.             apport.fileutils.report_dir = tempfile.mkdtemp()
  906.             self.orig_ignore_file = apport.report._ignore_file
  907.             (fd, apport.report._ignore_file) = tempfile.mkstemp()
  908.             os.close(fd)
  909.             self.orig_argv = sys.argv
  910.             sys.argv = [
  911.                 'ui-test']
  912.             self.ui = _TestSuiteUserInterface()
  913.             self.report = apport.Report()
  914.             self.report['Package'] = 'libfoo1 1-1'
  915.             self.report['SourcePackage'] = 'foo'
  916.             self.report['Foo'] = 'A' * 1000
  917.             self.report['CoreDump'] = 'A' * 100000
  918.             self.report_file = tempfile.NamedTemporaryFile()
  919.             self.update_report_file()
  920.  
  921.         
  922.         def update_report_file(self):
  923.             self.report_file.seek(0)
  924.             self.report_file.truncate()
  925.             self.report.write(self.report_file)
  926.             self.report_file.flush()
  927.  
  928.         
  929.         def tearDown(self):
  930.             sys.argv = self.orig_argv
  931.             shutil.rmtree(apport.fileutils.report_dir)
  932.             apport.fileutils.report_dir = self.orig_report_dir
  933.             self.orig_report_dir = None
  934.             os.unlink(apport.report._ignore_file)
  935.             apport.report._ignore_file = self.orig_ignore_file
  936.             self.ui = None
  937.             self.report_file.close()
  938.             self.assertEqual(subprocess.call([
  939.                 'pidof',
  940.                 '/bin/cat']), 1, 'no stray cats')
  941.             self.assertEqual(subprocess.call([
  942.                 'pidof',
  943.                 '/bin/sleep']), 1, 'no stray sleeps')
  944.  
  945.         
  946.         def test_format_filesize(self):
  947.             '''format_filesize().'''
  948.             self.assertEqual(self.ui.format_filesize(0), '0.0 KiB')
  949.             self.assertEqual(self.ui.format_filesize(2048), '2.0 KiB')
  950.             self.assertEqual(self.ui.format_filesize(2560), '2.5 KiB')
  951.             self.assertEqual(self.ui.format_filesize(1000000), '976.6 KiB')
  952.             self.assertEqual(self.ui.format_filesize(1048576), '1.0 MiB')
  953.             self.assertEqual(self.ui.format_filesize(2.83116e+06), '2.7 MiB')
  954.             self.assertEqual(self.ui.format_filesize(1073741824), '1.0 GiB')
  955.             self.assertEqual(self.ui.format_filesize(0xA0000000L), '2.5 GiB')
  956.  
  957.         
  958.         def test_get_size_loaded(self):
  959.             '''get_complete_size() and get_reduced_size() for loaded Reports.'''
  960.             self.ui.load_report(self.report_file.name)
  961.             self.assertEqual(self.ui.get_complete_size(), os.path.getsize(self.report_file.name))
  962.             rs = self.ui.get_reduced_size()
  963.             self.assert_(rs > 1000)
  964.             self.assert_(rs < 10000)
  965.  
  966.         
  967.         def test_get_size_constructed(self):
  968.             '''get_complete_size() and get_reduced_size() for on-the-fly Reports.'''
  969.             self.ui.report = apport.Report('Bug')
  970.             self.ui.report['Hello'] = 'World'
  971.             s = self.ui.get_complete_size()
  972.             self.assert_(s > 5)
  973.             self.assert_(s < 100)
  974.             self.assertEqual(s, self.ui.get_reduced_size())
  975.  
  976.         
  977.         def test_load_report(self):
  978.             '''load_report().'''
  979.             self.ui.load_report(self.report_file.name)
  980.             self.assertEqual(self.ui.report, self.report)
  981.             self.assertEqual(self.ui.msg_title, None)
  982.             del self.report['Package']
  983.             del self.report['SourcePackage']
  984.             self.update_report_file()
  985.             self.ui.load_report(self.report_file.name)
  986.             self.assert_(self.ui.report == None)
  987.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  988.             self.assertEqual(self.ui.msg_severity, 'info')
  989.             self.ui.clear_msg()
  990.             self.report_file.seek(0)
  991.             self.report_file.truncate()
  992.             self.report_file.write('Type: test\nPackage: foo 1-1\nCoreDump: base64\n bOgUs=\n')
  993.             self.report_file.flush()
  994.             self.ui.load_report(self.report_file.name)
  995.             self.assert_(self.ui.report == None)
  996.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  997.             self.assertEqual(self.ui.msg_severity, 'error')
  998.  
  999.         
  1000.         def test_restart(self):
  1001.             '''restart().'''
  1002.             p = os.path.join(apport.fileutils.report_dir, 'ProcCmdline')
  1003.             r = os.path.join(apport.fileutils.report_dir, 'Custom')
  1004.             self.report['ProcCmdline'] = 'touch ' + p
  1005.             self.update_report_file()
  1006.             self.ui.load_report(self.report_file.name)
  1007.             self.ui.restart()
  1008.             time.sleep(1)
  1009.             self.assert_(os.path.exists(p))
  1010.             self.assert_(not os.path.exists(r))
  1011.             os.unlink(p)
  1012.             self.report['RespawnCommand'] = 'touch ' + r
  1013.             self.update_report_file()
  1014.             self.ui.load_report(self.report_file.name)
  1015.             self.ui.restart()
  1016.             time.sleep(1)
  1017.             self.assert_(not os.path.exists(p))
  1018.             self.assert_(os.path.exists(r))
  1019.             os.unlink(r)
  1020.             del self.report['RespawnCommand']
  1021.             self.report['ProcCmdline'] = '/nonexisting'
  1022.             self.update_report_file()
  1023.             self.ui.load_report(self.report_file.name)
  1024.  
  1025.         
  1026.         def test_collect_info_distro(self):
  1027.             '''collect_info() on report without information (distro bug).'''
  1028.             self.ui.report = apport.Report()
  1029.             self.ui.collect_info()
  1030.             self.assert_(set([
  1031.                 'Date',
  1032.                 'Uname',
  1033.                 'DistroRelease',
  1034.                 'ProblemType']).issubset(set(self.ui.report.keys())))
  1035.             self.assertEqual(self.ui.ic_progress_pulses, 0, 'no progress dialog for distro bug info collection')
  1036.  
  1037.         
  1038.         def test_collect_info_exepath(self):
  1039.             '''collect_info() on report with only ExecutablePath.'''
  1040.             self.report = apport.Report()
  1041.             self.report['ExecutablePath'] = '/bin/bash'
  1042.             self.update_report_file()
  1043.             self.ui.load_report(self.report_file.name)
  1044.             self.ui.report['Fstab'] = ('/etc/fstab', True)
  1045.             self.ui.report['CompressedValue'] = problem_report.CompressedValue('Test')
  1046.             self.ui.collect_info()
  1047.             self.assert_(set([
  1048.                 'SourcePackage',
  1049.                 'Package',
  1050.                 'ProblemType',
  1051.                 'Uname',
  1052.                 'Dependencies',
  1053.                 'DistroRelease',
  1054.                 'Date',
  1055.                 'ExecutablePath']).issubset(set(self.ui.report.keys())))
  1056.             self.assert_(self.ui.ic_progress_pulses > 0, 'progress dialog for package bug info collection')
  1057.             self.assertEqual(self.ui.ic_progress_active, False, 'progress dialog for package bug info collection finished')
  1058.  
  1059.         
  1060.         def test_collect_info_package(self):
  1061.             '''collect_info() on report with a package.'''
  1062.             self.ui.report = apport.Report()
  1063.             self.ui.cur_package = 'bash'
  1064.             self.ui.collect_info()
  1065.             self.assert_(set([
  1066.                 'SourcePackage',
  1067.                 'Package',
  1068.                 'ProblemType',
  1069.                 'Uname',
  1070.                 'Dependencies',
  1071.                 'DistroRelease',
  1072.                 'Date']).issubset(set(self.ui.report.keys())))
  1073.             self.assert_(self.ui.ic_progress_pulses > 0, 'progress dialog for package bug info collection')
  1074.             self.assertEqual(self.ui.ic_progress_active, False, 'progress dialog for package bug info collection finished')
  1075.  
  1076.         
  1077.         def test_handle_duplicate(self):
  1078.             '''handle_duplicate().'''
  1079.             self.ui.load_report(self.report_file.name)
  1080.             self.assertEqual(self.ui.handle_duplicate(), False)
  1081.             self.assertEqual(self.ui.msg_title, None)
  1082.             self.assertEqual(self.ui.opened_url, None)
  1083.             demo_url = 'http://example.com/1'
  1084.             self.report['BugPatternURL'] = demo_url
  1085.             self.update_report_file()
  1086.             self.ui.load_report(self.report_file.name)
  1087.             self.assertEqual(self.ui.handle_duplicate(), True)
  1088.             self.assertEqual(self.ui.msg_severity, 'info')
  1089.             self.assertEqual(self.ui.opened_url, demo_url)
  1090.  
  1091.         
  1092.         def test_run_nopending(self):
  1093.             '''running the frontend without any pending reports.'''
  1094.             sys.argv = []
  1095.             self.ui = _TestSuiteUserInterface()
  1096.             self.assertEqual(self.ui.run_argv(), False)
  1097.  
  1098.         
  1099.         def test_run_report_bug_noargs(self):
  1100.             '''run_report_bug() without specifying arguments.'''
  1101.             sys.argv = [
  1102.                 'ui-test',
  1103.                 '-f']
  1104.             self.ui = _TestSuiteUserInterface()
  1105.             self.assertEqual(self.ui.run_argv(), False)
  1106.             self.assertEqual(self.ui.msg_severity, 'error')
  1107.  
  1108.         
  1109.         def test_run_report_bug_package(self):
  1110.             '''run_report_bug() for a package.'''
  1111.             sys.argv = [
  1112.                 'ui-test',
  1113.                 '-f',
  1114.                 '-p',
  1115.                 'bash']
  1116.             self.ui = _TestSuiteUserInterface()
  1117.             self.assertEqual(self.ui.run_argv(), True)
  1118.             self.assertEqual(self.ui.msg_severity, None)
  1119.             self.assertEqual(self.ui.msg_title, None)
  1120.             self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1121.             self.assert_(self.ui.ic_progress_pulses > 0)
  1122.             self.assertEqual(self.ui.report['SourcePackage'], 'bash')
  1123.             self.assert_('Dependencies' in self.ui.report.keys())
  1124.             self.assert_('ProcEnviron' in self.ui.report.keys())
  1125.             self.assertEqual(self.ui.report['ProblemType'], 'Bug')
  1126.             sys.argv = [
  1127.                 'ui-test',
  1128.                 '-f',
  1129.                 '-p',
  1130.                 'nonexisting_gibberish']
  1131.             self.ui = _TestSuiteUserInterface()
  1132.             self.ui.run_argv()
  1133.             self.assertEqual(self.ui.msg_severity, 'error')
  1134.  
  1135.         
  1136.         def test_run_report_bug_pid(self):
  1137.             '''run_report_bug() for a pid.'''
  1138.             pid = os.fork()
  1139.             if pid == 0:
  1140.                 os.execv('/bin/sleep', [
  1141.                     'sleep',
  1142.                     '10000'])
  1143.                 if not False:
  1144.                     raise AssertionError, 'Could not execute /bin/sleep'
  1145.             
  1146.             time.sleep(0.5)
  1147.             
  1148.             try:
  1149.                 sys.argv = [
  1150.                     'ui-test',
  1151.                     '-f',
  1152.                     '-P',
  1153.                     str(pid)]
  1154.                 self.ui = _TestSuiteUserInterface()
  1155.                 self.assertEqual(self.ui.run_argv(), True)
  1156.             finally:
  1157.                 os.kill(pid, signal.SIGKILL)
  1158.                 os.waitpid(pid, 0)
  1159.  
  1160.             self.assert_('SourcePackage' in self.ui.report.keys())
  1161.             self.assert_('Dependencies' in self.ui.report.keys())
  1162.             self.assert_('ProcMaps' in self.ui.report.keys())
  1163.             self.assertEqual(self.ui.report['ExecutablePath'], '/bin/sleep')
  1164.             self.failIf(self.ui.report.has_key('ProcCmdline'))
  1165.             self.assert_('ProcEnviron' in self.ui.report.keys())
  1166.             self.assertEqual(self.ui.report['ProblemType'], 'Bug')
  1167.             self.assertEqual(self.ui.msg_severity, None)
  1168.             self.assertEqual(self.ui.msg_title, None)
  1169.             self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1170.             self.assert_(self.ui.ic_progress_pulses > 0)
  1171.  
  1172.         
  1173.         def test_run_report_bug_wrong_pid(self):
  1174.             '''run_report_bug() for a nonexisting pid.'''
  1175.             pid = 1
  1176.             while True:
  1177.                 pid += 1
  1178.                 
  1179.                 try:
  1180.                     os.kill(pid, 0)
  1181.                 continue
  1182.                 except OSError:
  1183.                     e = None
  1184.                     if e.errno == errno.ESRCH:
  1185.                         break
  1186.                     
  1187.                     e.errno == errno.ESRCH
  1188.                 
  1189.  
  1190.                 None<EXCEPTION MATCH>OSError
  1191.             sys.argv = [
  1192.                 'ui-test',
  1193.                 '-f',
  1194.                 '-P',
  1195.                 str(pid)]
  1196.             self.ui = _TestSuiteUserInterface()
  1197.             self.ui.run_argv()
  1198.  
  1199.         
  1200.         def test_run_report_bug_noperm_pid(self):
  1201.             '''run_report_bug() for a pid which runs as a different user.'''
  1202.             if not os.getuid() > 0:
  1203.                 raise AssertionError, 'this test must not be run as root'
  1204.             sys.argv = [
  1205.                 'ui-test',
  1206.                 '-f',
  1207.                 '-P',
  1208.                 '1']
  1209.             self.ui = _TestSuiteUserInterface()
  1210.             self.ui.run_argv()
  1211.             self.assertEqual(self.ui.msg_severity, 'error')
  1212.  
  1213.         
  1214.         def test_run_report_bug_unpackaged_pid(self):
  1215.             '''run_report_bug() for a pid of an unpackaged program.'''
  1216.             (fd, exename) = tempfile.mkstemp()
  1217.             os.write(fd, open('/bin/cat').read())
  1218.             os.close(fd)
  1219.             os.chmod(exename, 493)
  1220.             pid = os.fork()
  1221.             if pid == 0:
  1222.                 os.execv(exename, [
  1223.                     exename])
  1224.             
  1225.             
  1226.             try:
  1227.                 sys.argv = [
  1228.                     'ui-test',
  1229.                     '-f',
  1230.                     '-P',
  1231.                     str(pid)]
  1232.                 self.ui = _TestSuiteUserInterface()
  1233.                 self.assertRaises(SystemExit, self.ui.run_argv)
  1234.             finally:
  1235.                 os.kill(pid, signal.SIGKILL)
  1236.                 os.wait()
  1237.                 os.unlink(exename)
  1238.  
  1239.             self.assertEqual(self.ui.msg_severity, 'error')
  1240.  
  1241.         
  1242.         def _gen_test_crash(self):
  1243.             '''Generate a Report with real crash data.'''
  1244.             test_executable = '/bin/cat'
  1245.             if not os.access(test_executable, os.X_OK):
  1246.                 raise AssertionError, test_executable + ' is not executable'
  1247.             pid = os.fork()
  1248.             
  1249.             try:
  1250.                 time.sleep(0.5)
  1251.                 coredump = os.path.join(apport.fileutils.report_dir, 'core')
  1252.                 if not subprocess.call([
  1253.                     'gdb',
  1254.                     '--batch',
  1255.                     '--ex',
  1256.                     'generate-core-file ' + coredump,
  1257.                     test_executable,
  1258.                     str(pid)], stdout = subprocess.PIPE, stderr = subprocess.PIPE) == 0:
  1259.                     raise AssertionError
  1260.                 r = apport.Report()
  1261.                 r['ExecutablePath'] = test_executable
  1262.                 r['CoreDump'] = (coredump,)
  1263.                 r['Signal'] = '11'
  1264.                 r.add_proc_info(pid)
  1265.                 r.add_user_info()
  1266.             finally:
  1267.                 os.kill(pid, signal.SIGKILL)
  1268.                 os.waitpid(pid, 0)
  1269.  
  1270.             return r
  1271.  
  1272.         
  1273.         def test_run_crash(self):
  1274.             '''run_crash().'''
  1275.             r = self._gen_test_crash()
  1276.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1277.             r.write(open(report_file, 'w'))
  1278.             self.ui = _TestSuiteUserInterface()
  1279.             self.ui.present_crash_response = {
  1280.                 'action': 'cancel',
  1281.                 'blacklist': False }
  1282.             self.ui.run_crash(report_file)
  1283.             self.assertEqual(self.ui.msg_severity, None)
  1284.             self.assertEqual(self.ui.msg_title, None)
  1285.             self.assertEqual(self.ui.opened_url, None)
  1286.             self.assertEqual(self.ui.ic_progress_pulses, 0)
  1287.             r.write(open(report_file, 'w'))
  1288.             self.ui = _TestSuiteUserInterface()
  1289.             self.ui.present_crash_response = {
  1290.                 'action': 'report',
  1291.                 'blacklist': False }
  1292.             self.ui.present_details_response = 'cancel'
  1293.             self.ui.run_crash(report_file)
  1294.             self.assertEqual(self.ui.msg_severity, None, 'has %s message: %s: %s' % (self.ui.msg_severity, str(self.ui.msg_title), str(self.ui.msg_text)))
  1295.             self.assertEqual(self.ui.msg_title, None)
  1296.             self.assertEqual(self.ui.opened_url, None)
  1297.             self.assertNotEqual(self.ui.ic_progress_pulses, 0)
  1298.             r.write(open(report_file, 'w'))
  1299.             self.ui = _TestSuiteUserInterface()
  1300.             self.ui.present_crash_response = {
  1301.                 'action': 'report',
  1302.                 'blacklist': False }
  1303.             self.ui.present_details_response = 'full'
  1304.             self.ui.run_crash(report_file)
  1305.             self.assertEqual(self.ui.msg_severity, None)
  1306.             self.assertEqual(self.ui.msg_title, None)
  1307.             self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1308.             self.assertNotEqual(self.ui.ic_progress_pulses, 0)
  1309.             self.assert_('SourcePackage' in self.ui.report.keys())
  1310.             self.assert_('Dependencies' in self.ui.report.keys())
  1311.             self.assert_('Stacktrace' in self.ui.report.keys())
  1312.             self.assert_('ProcEnviron' in self.ui.report.keys())
  1313.             self.assertEqual(self.ui.report['ProblemType'], 'Crash')
  1314.             self.assert_(len(self.ui.report['CoreDump']) > 10000)
  1315.             self.assert_(self.ui.report['Title'].startswith('cat crashed with SIGSEGV'))
  1316.             r.write(open(report_file, 'w'))
  1317.             self.ui = _TestSuiteUserInterface()
  1318.             self.ui.present_crash_response = {
  1319.                 'action': 'report',
  1320.                 'blacklist': False }
  1321.             self.ui.present_details_response = 'reduced'
  1322.             self.ui.run_crash(report_file)
  1323.             self.assertEqual(self.ui.msg_severity, None)
  1324.             self.assertEqual(self.ui.msg_title, None)
  1325.             self.assertEqual(self.ui.opened_url, 'http://coreutils.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1326.             self.assertNotEqual(self.ui.ic_progress_pulses, 0)
  1327.             self.assert_('SourcePackage' in self.ui.report.keys())
  1328.             self.assert_('Dependencies' in self.ui.report.keys())
  1329.             self.assert_('Stacktrace' in self.ui.report.keys())
  1330.             self.assertEqual(self.ui.report['ProblemType'], 'Crash')
  1331.             self.assert_(not self.ui.report.has_key('CoreDump'))
  1332.             self.assert_(not self.ui.report.check_ignored())
  1333.             r.write(open(report_file, 'w'))
  1334.             self.ui = _TestSuiteUserInterface()
  1335.             self.ui.present_crash_response = {
  1336.                 'action': 'cancel',
  1337.                 'blacklist': True }
  1338.             self.ui.run_crash(report_file)
  1339.             self.assertEqual(self.ui.msg_severity, None)
  1340.             self.assertEqual(self.ui.msg_title, None)
  1341.             self.assertEqual(self.ui.opened_url, None)
  1342.             self.assertEqual(self.ui.ic_progress_pulses, 0)
  1343.             self.assert_(self.ui.report.check_ignored())
  1344.  
  1345.         
  1346.         def test_run_crash_argv_file(self):
  1347.             '''run_crash() through a file specified on the command line.'''
  1348.             self.report['Package'] = 'bash'
  1349.             self.report['UnsupportableReason'] = 'It stinks.'
  1350.             self.update_report_file()
  1351.             sys.argv = [
  1352.                 'ui-test',
  1353.                 '-c',
  1354.                 self.report_file.name]
  1355.             self.ui = _TestSuiteUserInterface()
  1356.             self.assertEqual(self.ui.run_argv(), True)
  1357.             self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' % (self.ui.msg_title, self.ui.msg_text))
  1358.             self.assertEqual(self.ui.msg_severity, 'info')
  1359.             sys.argv = [
  1360.                 'ui-test',
  1361.                 '-c',
  1362.                 '/nonexisting.crash']
  1363.             self.ui = _TestSuiteUserInterface()
  1364.             self.assertEqual(self.ui.run_argv(), True)
  1365.             self.assertEqual(self.ui.msg_severity, 'error')
  1366.  
  1367.         
  1368.         def test_run_crash_unsupportable(self):
  1369.             '''run_crash() on a crash with the UnsupportableReason
  1370.             field.'''
  1371.             self.report['UnsupportableReason'] = 'It stinks.'
  1372.             self.report['Package'] = 'bash'
  1373.             self.update_report_file()
  1374.             self.ui.run_crash(self.report_file.name)
  1375.             self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' % (self.ui.msg_title, self.ui.msg_text))
  1376.             self.assertEqual(self.ui.msg_severity, 'info')
  1377.  
  1378.         
  1379.         def test_run_crash_unreportable(self):
  1380.             '''run_crash() on a crash with the UnreportableReason
  1381.             field.'''
  1382.             self.report['UnreportableReason'] = 'It stinks.'
  1383.             self.report['ExecutablePath'] = '/bin/bash'
  1384.             self.report['Package'] = 'bash 1'
  1385.             self.update_report_file()
  1386.             self.ui.present_crash_response = {
  1387.                 'action': 'report',
  1388.                 'blacklist': False }
  1389.             self.ui.present_details_response = 'full'
  1390.             self.ui.run_crash(self.report_file.name)
  1391.             self.assert_('It stinks.' in self.ui.msg_text, '%s: %s' % (self.ui.msg_title, self.ui.msg_text))
  1392.             self.assertEqual(self.ui.msg_severity, 'info')
  1393.  
  1394.         
  1395.         def test_run_crash_ignore(self):
  1396.             '''run_crash() on a crash with the Ignore field.'''
  1397.             self.report['Ignore'] = 'True'
  1398.             self.report['ExecutablePath'] = '/bin/bash'
  1399.             self.report['Package'] = 'bash 1'
  1400.             self.update_report_file()
  1401.             self.ui.run_crash(self.report_file.name)
  1402.             self.assertEqual(self.ui.msg_severity, None)
  1403.  
  1404.         
  1405.         def test_run_crash_nocore(self):
  1406.             '''run_crash() for a crash dump without CoreDump.'''
  1407.             test_executable = '/bin/cat'
  1408.             if not os.access(test_executable, os.X_OK):
  1409.                 raise AssertionError, test_executable + ' is not executable'
  1410.             pid = os.fork()
  1411.             
  1412.             try:
  1413.                 time.sleep(0.5)
  1414.                 r = apport.Report()
  1415.                 r['ExecutablePath'] = test_executable
  1416.                 r['Signal'] = '42'
  1417.                 r.add_proc_info(pid)
  1418.                 r.add_user_info()
  1419.             finally:
  1420.                 os.kill(pid, signal.SIGKILL)
  1421.                 os.waitpid(pid, 0)
  1422.  
  1423.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1424.             r.write(open(report_file, 'w'))
  1425.             self.ui = _TestSuiteUserInterface()
  1426.             self.ui.run_crash(report_file)
  1427.             self.assertEqual(self.ui.msg_severity, 'error')
  1428.             self.assert_('memory' in self.ui.msg_text, '%s: %s' % (self.ui.msg_title, self.ui.msg_text))
  1429.  
  1430.         
  1431.         def test_run_crash_preretraced(self):
  1432.             '''run_crash() pre-retraced reports.
  1433.             
  1434.             This happens with crashes which are pre-processed by
  1435.             apport-retrace.'''
  1436.             r = self._gen_test_crash()
  1437.             r.add_gdb_info()
  1438.             del r['CoreDump']
  1439.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1440.             r.write(open(report_file, 'w'))
  1441.             self.ui = _TestSuiteUserInterface()
  1442.             self.ui.present_crash_response = {
  1443.                 'action': 'report',
  1444.                 'blacklist': False }
  1445.             self.ui.present_details_response = 'cancel'
  1446.             self.ui.run_crash(report_file)
  1447.             self.assertEqual(self.ui.msg_severity, None, 'has %s message: %s: %s' % (self.ui.msg_severity, str(self.ui.msg_title), str(self.ui.msg_text)))
  1448.             self.assertEqual(self.ui.msg_title, None)
  1449.             self.assertEqual(self.ui.opened_url, None)
  1450.             self.assertEqual(self.ui.ic_progress_pulses, 0)
  1451.  
  1452.         
  1453.         def test_run_crash_errors(self):
  1454.             '''run_crash() on various error conditions.'''
  1455.             r = apport.Report()
  1456.             r['ExecutablePath'] = '/bin/bash'
  1457.             r['Package'] = 'foobarbaz'
  1458.             r['SourcePackage'] = 'foobarbaz'
  1459.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1460.             r.write(open(report_file, 'w'))
  1461.             self.ui.run_crash(report_file)
  1462.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  1463.             self.assertEqual(self.ui.msg_severity, 'error')
  1464.  
  1465.         
  1466.         def test_run_crash_uninstalled(self):
  1467.             '''run_crash() on reports with subsequently uninstalled packages'''
  1468.             r = self._gen_test_crash()
  1469.             r['ExecutablePath'] = '/bin/nonexisting'
  1470.             r['Package'] = 'bash'
  1471.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1472.             r.write(open(report_file, 'w'))
  1473.             self.ui.present_crash_response = {
  1474.                 'action': 'report',
  1475.                 'blacklist': False }
  1476.             self.ui.run_crash(report_file)
  1477.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  1478.             self.assertEqual(self.ui.msg_severity, 'info')
  1479.             r = apport.Report()
  1480.             r['ExecutablePath'] = '/bin/nonexisting'
  1481.             r['InterpreterPath'] = '/usr/bin/python'
  1482.             r['Traceback'] = 'ZeroDivisionError: integer division or modulo by zero'
  1483.             self.ui.run_crash(report_file)
  1484.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  1485.             self.assertEqual(self.ui.msg_severity, 'info')
  1486.             r = apport.Report()
  1487.             r['ExecutablePath'] = '/bin/sh'
  1488.             r['InterpreterPath'] = '/usr/bin/nonexisting'
  1489.             r['Traceback'] = 'ZeroDivisionError: integer division or modulo by zero'
  1490.             self.ui.run_crash(report_file)
  1491.             self.assertEqual(self.ui.msg_title, _('Invalid problem report'))
  1492.             self.assertEqual(self.ui.msg_severity, 'info')
  1493.  
  1494.         
  1495.         def test_run_crash_package(self):
  1496.             '''run_crash() for a package error.'''
  1497.             r = apport.Report('Package')
  1498.             r['Package'] = 'bash'
  1499.             r['SourcePackage'] = 'bash'
  1500.             r['ErrorMessage'] = 'It broke'
  1501.             r['VarLogPackagerlog'] = 'foo\nbar'
  1502.             r.add_os_info()
  1503.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1504.             r.write(open(report_file, 'w'))
  1505.             self.ui = _TestSuiteUserInterface()
  1506.             self.ui.present_package_error_response = 'cancel'
  1507.             self.ui.run_crash(report_file)
  1508.             self.assertEqual(self.ui.msg_severity, None)
  1509.             self.assertEqual(self.ui.msg_title, None)
  1510.             self.assertEqual(self.ui.opened_url, None)
  1511.             self.assertEqual(self.ui.ic_progress_pulses, 0)
  1512.             r.write(open(report_file, 'w'))
  1513.             self.ui = _TestSuiteUserInterface()
  1514.             self.ui.present_package_error_response = 'report'
  1515.             self.ui.run_crash(report_file)
  1516.             self.assertEqual(self.ui.msg_severity, None)
  1517.             self.assertEqual(self.ui.msg_title, None)
  1518.             self.assertEqual(self.ui.opened_url, 'http://bash.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1519.             self.assert_('SourcePackage' in self.ui.report.keys())
  1520.             self.assert_('Package' in self.ui.report.keys())
  1521.             self.assertEqual(self.ui.report['ProblemType'], 'Package')
  1522.             self.assert_('Architecture' in self.ui.report.keys())
  1523.             self.assert_('DistroRelease' in self.ui.report.keys())
  1524.             self.assert_('Uname' in self.ui.report.keys())
  1525.  
  1526.         
  1527.         def test_run_crash_kernel(self):
  1528.             '''run_crash() for a kernel error.'''
  1529.             r = apport.Report('KernelCrash')
  1530.             r['Package'] = apport.packaging.get_kernel_package()
  1531.             r['SourcePackage'] = 'linux'
  1532.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1533.             r.write(open(report_file, 'w'))
  1534.             self.ui = _TestSuiteUserInterface()
  1535.             self.ui.present_kernel_error_response = 'cancel'
  1536.             self.ui.run_crash(report_file)
  1537.             self.assertEqual(self.ui.msg_severity, None, 'error: %s - %s' % (self.ui.msg_title, self.ui.msg_text))
  1538.             self.assertEqual(self.ui.msg_title, None)
  1539.             self.assertEqual(self.ui.opened_url, None)
  1540.             self.assertEqual(self.ui.ic_progress_pulses, 0)
  1541.             r.write(open(report_file, 'w'))
  1542.             self.ui = _TestSuiteUserInterface()
  1543.             self.ui.present_kernel_error_response = 'report'
  1544.             self.ui.present_details_response = 'full'
  1545.             self.ui.run_crash(report_file)
  1546.             self.assertEqual(self.ui.msg_severity, None, str(self.ui.msg_title) + ' ' + str(self.ui.msg_text))
  1547.             self.assertEqual(self.ui.msg_title, None)
  1548.             self.assertEqual(self.ui.opened_url, 'http://linux.bugs.example.com/%i' % self.ui.crashdb.latest_id())
  1549.             self.assert_('SourcePackage' in self.ui.report.keys())
  1550.             self.assert_('ProcModules' in self.ui.report.keys())
  1551.             self.assert_('Lspci' in self.ui.report.keys())
  1552.             self.assertEqual(self.ui.report['ProblemType'], 'KernelCrash')
  1553.  
  1554.         
  1555.         def test_run_crash_anonymity(self):
  1556.             '''run_crash() anonymization.'''
  1557.             r = self._gen_test_crash()
  1558.             report_file = os.path.join(apport.fileutils.report_dir, 'test.crash')
  1559.             r.write(open(report_file, 'w'))
  1560.             self.ui = _TestSuiteUserInterface()
  1561.             self.ui.present_crash_response = {
  1562.                 'action': 'report',
  1563.                 'blacklist': False }
  1564.             self.ui.present_details_response = 'cancel'
  1565.             self.ui.run_crash(report_file)
  1566.             self.failIf('ProcCwd' in self.ui.report)
  1567.             dump = StringIO()
  1568.             self.ui.report.write(dump)
  1569.             p = pwd.getpwuid(os.getuid())
  1570.             bad_strings = [
  1571.                 os.uname()[1],
  1572.                 p[0],
  1573.                 p[4],
  1574.                 p[5],
  1575.                 os.getcwd()]
  1576.             for s in bad_strings:
  1577.                 self.failIf(s in dump.getvalue(), 'dump contains sensitive string: %s' % s)
  1578.             
  1579.  
  1580.  
  1581.     unittest.main()
  1582.  
  1583.